<template>
  <div ref="root" class="com-waterfall">
    <div class="slot-box">
      <slot />
    </div>
  </div>
</template>

<script>
export default {
  name: 'Waterfall',
  props: {
    // 列数
    cols: {
      type: Number,
      default: 2
    },
    // 数据列表
    dataList: {
      type: Array,
      default: () => []
    },
    // 每一列的宽度
    width: Number,

    // 距离底部多远进行上拉刷新
    reachBottomDistance: {
      type: Number,
      default: 100
    },

    // 滚动节流间隔事件，ms
    interval: {
      type: Number,
      default: 100
    }
  },
  data() {
    return {
      root: null, // 组件根元素
      colNodes: [], // 每一列的元素
      canLoad: true, // 是否可以加载更多
      renderIndex: 0 // 已渲染的元素索引
    }
  },
  watch: {
  // 列数发生变化时，重新渲染
    cols(nVal, oVal) {
      if (nVal === oVal) {
        return
      }
      this.init()
    },
    // 当数据列表发生变化时
    // 若数据长度变短，则清除原来的列表，并重新渲染
    // 若长度变长，则是下拉加载更多的情况，直接从已经渲染的部分接着渲染
    dataList(nVal, oVal) {
      if (!nVal.length || nVal.length < oVal.length) {
        this.clearItem()
      }
      // 防止此时slot中元素还未渲染成功，获取不到其中元素的情况
      this.$nextTick(() => {
        setTimeout(() => {
          this.createColItem()
        }, 0)
      })
    }
  },
  mounted() {
    this.init()
    // 添加滚动事件
    this.scrollEvent()
  },
  methods: {
    init() {
      this.root = this.$refs.root
      // 初始化数据
      this.canLoad = true
      this.renderIndex = 0
      this.clearColumn()
      // 创建列
      this.createColumn()

      this.$nextTick(() => {
        setTimeout(() => {
          // 创建列中的元素
          this.createColItem()
        }, 0)
      })
    },

    // 清除列
    clearColumn() {
      this.colNodes.forEach((item) => {
        item.remove()
      })
      this.colNodes = []
    },
    // 清除列中元素
    clearItem() {
      if (!this.colNodes || !this.colNodes.length) {
        return
      }
      this.colNodes.forEach((item) => {
        // eslint-disable-next-line no-param-reassign
        item.innerHTML = ''
      })
      this.renderIndex = 0
      this.canLoad = true
    },

    // 创建列
    createColumn() {
      const colNodes = []
      for (let index = 0; index < this.cols; index += 1) {
        const node = document.createElement('div')
        node.className = 'com-waterfall-cols'
        // 若给定宽度，则按照给定宽度设置列宽度
        if (this.width) {
          node.style.width = `${this.width}px`
        } else {
          const width = Math.round((1 / this.cols) * 100)
          node.style.width = `${width}%`
        }
        if (!this.root) {
          this.root = this.$refs.root
        }
        // eslint-disable-next-line no-unused-expressions
        this.root && this.root.appendChild(node)
        colNodes.push(node)
      }
      this.colNodes = colNodes
    },
    // 创建列中元素
    async createColItem() {
      if (this.isRendering || !this.$slots.default || !this.$slots.default.length) {
        return
      }
      this.isRendering = true
      // nodes 获取子元素中的具体内容
      const nodes = this.$slots.default.splice(this.renderIndex)
      for (let index = 0; index < nodes.length; index += 1) {
        // 获取最短的列
        const shortCol = this.getShortColumns()
        if (!shortCol) {
          return
        }
        // 将元素放入短列中
        // eslint-disable-next-line no-await-in-loop
        await this.appendChild(shortCol, nodes[index].elm)
        this.renderIndex += 1
      }
      this.isRendering = false
      // 若未满一屏，则加载更多
      // eslint-disable-next-line no-underscore-dangle
      this.loadMores()
    },

    // 获取更矮的一列
    getShortColumns() {
      const { length } = this.colNodes
      if (!length) {
        return
      }
      let shortNode = this.colNodes[0]
      for (let index = 1; index < length; index += 1) {
        const height = this.colNodes[index].offsetHeight
        if (height < shortNode.offsetHeight) {
          shortNode = this.colNodes[index]
        }
      }
      // eslint-disable-next-line consistent-return
      return shortNode
    },

    // 将每个item放入列中
    async appendChild(parent, node) {
      if (!node || !(node instanceof HTMLElement)) {
        return
      }
      const imgs = node.getElementsByTagName('img')
      // 若不存在图片，则直接放入短列中
      if (!imgs || !imgs.length) {
        parent.appendChild(node)
        // eslint-disable-next-line consistent-return
        return Promise.resolve()
      }
      // 若存在图片，则找出图片的长度，并给图片占位，然后放入短列中
      for (let index = 0; index < imgs.length; index += 1) {
        const img = imgs[index]
        if (!img) {
          return
        }
      }
      parent.appendChild(node)
      // eslint-disable-next-line consistent-return
      return Promise.resolve()
    },

    scrollEvent() {
      window.onscroll = this.loadMore
      window.addEventListener('touchmove', this.loadMore, false)
    },

    // 滚动事件，节流
    loadMore() {
      this.$emit('scroll')
      if (this.timer) {
        return
      }
      this.timer = setTimeout(() => {
        clearTimeout(this.timer)
        this.timer = null
        this.loadMores()
      }, this.interval)
    },
    loadMores() {
      const height = window.innerHeight
      const { bottom } = this.root.getBoundingClientRect()
      if (bottom <= height + this.reachBottomDistance && this.canLoad) {
        this.canLoad = false
        this.$emit('loadmore')
      } else if (bottom > height + this.reachBottomDistance) {
        this.canLoad = true
      }
    }
  }
}
</script>

<style lang="less">
.com-waterfall {
  width: 100%;
  position: relative;
  overflow-y: scroll;
}
.com-waterfall-cols {
  float: left;
  margin: 0;
  padding: 0;
}
.slot-box {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}
</style>
